package com.sailing.xjpb.log.aspect;

import com.sailing.xjpb.common.exception.SailingException;
import com.sailing.xjpb.common.web.ResponseCode;
import com.sailing.xjpb.common.web.WebUtils;
import com.sailing.xjpb.ipaas.client.core.UserContext;
import com.sailing.xjpb.ipaas.client.entity.dto.SipUserCurrentInfoDTO;
import com.sailing.xjpb.log.annotation.LogAnnotation;
import com.sailing.xjpb.log.annotation.NoLogAnnotation;
import com.sailing.xjpb.log.config.SystemLogConfigProperties;
import com.sailing.xjpb.log.entity.bo.BusinessLoggerBO;
import com.sailing.xjpb.log.service.BusinessLoggerService;
import com.sailing.xjpb.log.util.LogSystemCodeEnum;
import com.sailing.xjpb.log.util.OperateEnum;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpMethod;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 拦截Controller方法 记录其方法名称 模块名称 以及请求人的信息 并发送给ipass进行存储
 *
 * @author sutao
 * createDate 2022/9/7 17:06
 */
@Aspect
@Order(1)
@Slf4j
public class RequestLogAspect {

    private final BusinessLoggerService businessLoggerService;

    public RequestLogAspect(BusinessLoggerService businessLoggerService) {
        this.businessLoggerService = businessLoggerService;
    }

    @Autowired
    private SystemLogConfigProperties xtSystemLogConfig;

    /**
     * 环绕通知 以没有被noLogAnnotation注解标识的controller，以及被ApiOpeartion标识的对象为代理目标
     *
     * @param pjp          被执行对象
     * @param apiOperation swagger注解
     * @return Object
     * @author sutao
     * createDate 2022/9/8 21:05
     * <p>
     * editDescription:
     * editUser sutao
     * editDate 2022/9/8 21:05
     */
    @Around("within(@org.springframework.web.bind.annotation.RestController * || @org.springframework.stereotype" +
            ".Controller *) &&@annotation(apiOperation) ")
    public Object process(ProceedingJoinPoint pjp, ApiOperation apiOperation) throws Throwable {
        //获取方法注解 判断方法或者类上是否含有NoLogAnnotation注解，如果有则直接执行方法，不做请求日志记录
        if (isNoLog(pjp)) {
            return pjp.proceed();
        }
        String authorization = UserContext.getAuthorization();
        if (StringUtils.isEmpty(authorization)) {
            // 没有token的不记录日志
            return pjp.proceed();
        }
        BusinessLoggerBO businessLoggerBO = new BusinessLoggerBO();
        Throwable throwable = null;
        Object result = null;
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = null;
        if (sra != null) {
            request = sra.getRequest();
        }
        if (request != null) {
            String requestURI = request.getRequestURI();
            //根据请求以及每个系统配置的需要排除日志处理的请求列表判断该请求是否需要排除处理
            if (checkUrl(requestURI)) {
                return pjp.proceed();
            }
            String systemId = request.getHeader("systemId");
            if (StringUtils.isEmpty(systemId)) {
                systemId = xtSystemLogConfig.getSystemId();
            }
            if (log.isDebugEnabled()) {
                log.debug("requestURI:{}", requestURI);
                log.debug("systemId:{}", systemId);
            }
            //获取用户信息
            SipUserCurrentInfoDTO currentUser = UserContext.currentUser();
            //获取调用方法上apiOperation相关信息作为方法说明的内容
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            String methodAnnotationValue = apiOperation.value();
            //获取所属类的Api注解说明 作为方法所属模块的内容
            String classAnnotationValue = getClassAnnotationValue(signature.getMethod().getDeclaringClass());
            //获取参数列表
            //获取自定义注解BusinessLogAnnotation信息补充ApiOperation中不足的信息
            setUserOperateInfo(businessLoggerBO, signature);
            businessLoggerBO.setIpAddress(WebUtils.getIpAddress(request));
            businessLoggerBO.setMethod(getHttpMethod(request.getMethod()));
            Object[] args = pjp.getArgs();
            setQueryParam(args, request, businessLoggerBO, signature);
            businessLoggerBO.setSipUserInfoDTO(currentUser);
            businessLoggerBO.setSystemId(systemId);
            businessLoggerBO.setToken(UserContext.getBearerToken());
            businessLoggerBO.setMethodDoc(methodAnnotationValue);
            businessLoggerBO.setClassDoc(classAnnotationValue);
            businessLoggerBO.setUrl(requestURI);
            businessLoggerBO.setUri(requestURI);
            try {
                result = pjp.proceed();
                businessLoggerBO.setResponseBody(result);
            } catch (Exception e) {
                //获取异常记录异常 并抛出异常 给异常拦截器
                businessLoggerBO.setResponseBody(ExceptionUtils.getMessage(e));
                businessLoggerBO.setResponseCode(getResponseCode(e).toString());
                throwable = e;
                throw e;
            } finally {
                //调用日志信息处理方法
                businessLoggerService.log(businessLoggerBO, throwable);
            }

        }
        return result;

    }

    /**
     * 获取由Api注解修饰的注解信息
     *
     * @param declaringClass controller对应的class类
     * @return String
     * @author sutao
     * createDate 2022/9/9 13:41
     * <p>
     * editDescription:
     * editUser sutao
     * editDate 2022/9/9 13:41
     */
    private String getClassAnnotationValue(Class<?> declaringClass) {
        Api apiAnnotation = AnnotationUtils.findAnnotation(declaringClass, Api.class);
        String[] tags = new String[0];
        if (apiAnnotation != null) {
            tags = apiAnnotation.tags();
        }
        return tags.length > 0 ? tags[0] : null;
    }

    /**
     * 设置用户自定义注解中的操作类型信息
     *
     * @param businessLoggerBO 日志信息BO
     * @param signature        被代理对象签名抽象类
     * @author sutao
     * createDate 2022/9/9 13:23
     * <p>
     * editDescription:
     * editUser sutao
     * editDate 2022/9/9 13:23
     */
    private void setUserOperateInfo(BusinessLoggerBO businessLoggerBO, MethodSignature signature) {
        LogAnnotation businessLogAnnotation = AnnotationUtils.findAnnotation(signature.getMethod(),
                LogAnnotation.class);
        if (businessLogAnnotation != null) {
            OperateEnum operateEnum = businessLogAnnotation.operateType();
            businessLoggerBO.setOperateEnum(operateEnum);
        }
    }

    /**
     * 判断是否包含noLog标识
     *
     * @param pjp 切入点
     * @author sutao
     * createDate 2022/9/9 12:59
     * <p>
     * editDescription:
     * editUser sutao
     * editDate 2022/9/9 12:59
     */
    private boolean isNoLog(ProceedingJoinPoint pjp) {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        NoLogAnnotation methodNoLogAnnotation = AnnotationUtils.findAnnotation(method, NoLogAnnotation.class);
        NoLogAnnotation classNoLogAnnotation = AnnotationUtils.findAnnotation(method.getDeclaringClass(),
                NoLogAnnotation.class);
        return methodNoLogAnnotation != null || classNoLogAnnotation != null;
    }


    /**
     * 检查请求路径是否是黑名单中的路径
     *
     * @param requestURI 请求路径
     * @return boolean
     * @author sutao
     * createDate 2022/9/8 22:00
     * <p>
     * editDescription:
     * editUser sutao
     * editDate 2022/9/8 22:00
     */
    private boolean checkUrl(String requestURI) {
        List<String> whiteList = xtSystemLogConfig.getWhiteList();
        return whiteList.stream().anyMatch(requestURI::contains);
    }

    /**
     * 通过对比获取异常编码
     *
     * @param result 该参数一搬是exception类型
     * @author sutao
     * createDate 2022/9/8 22:02
     * <p>
     * editDescription:
     * editUser sutao
     * editDate 2022/9/8 22:02
     */
    private Integer getResponseCode(Object result) {
        int code = Integer.parseInt(LogSystemCodeEnum.RESPONSE_SUCCESS.getCodeValue());
        if (result instanceof SailingException) {
            code = ((SailingException) result).getResponseCode();
        } else if (result instanceof MethodArgumentNotValidException) {
            code = ResponseCode.B_3102.code();
        } else if (result instanceof BindException) {
            code = ResponseCode.B_3101.code();
        } else if (result instanceof MissingServletRequestParameterException) {
            code = ResponseCode.B_3101.code();
        } else if (result instanceof MultipartException) {
            code = ResponseCode.B_3102.code();
        } else if (result instanceof Exception) {
            code = ResponseCode.SYSTEM_ERROR.code();
        }
        return code;
    }

    /**
     * 获取请求的参数列表
     *
     * @param args             参数信息
     * @param request          请求
     * @param businessLoggerBO 日志信息
     * @author sutao
     * createDate 2022/9/8 22:03
     * <p>
     * editDescription:
     * editUser sutao
     * editDate 2022/9/8 22:03
     */
    private void setQueryParam(Object[] args, HttpServletRequest request, BusinessLoggerBO businessLoggerBO,
                               MethodSignature signature) {
        LogAnnotation businessLogAnnotation = AnnotationUtils.findAnnotation(signature.getMethod(),
                LogAnnotation.class);
        HttpMethod method = businessLoggerBO.getMethod();
        if (businessLogAnnotation == null || businessLogAnnotation.isRecordQueryParam()) {
            Map<String, String[]> parameterMap = request.getParameterMap();
            Map<String, Object> requestParams = new HashMap<>(parameterMap.size());
            parameterMap.forEach((k, v) -> requestParams.put(k, StringUtils.join(v, ",")));
            businessLoggerBO.setQueryParam(requestParams);
        }
        if (method != HttpMethod.GET && (businessLogAnnotation == null || businessLogAnnotation.isRecordRequestBody())) {
            Optional<Object> requestBody = getRequestBody(args);
            requestBody.ifPresent(businessLoggerBO::setRequestBody);
        }
    }

    /**
     * 获取请求body对应的VO
     *
     * @param args 参数信息
     * @return 请求体对应的VO
     */
    public Optional<Object> getRequestBody(Object[] args) {
        return Arrays.stream(args).filter(arg ->
                !(arg instanceof Pageable
                        || arg instanceof HttpServletRequest
                        || arg instanceof HttpServletResponse
                        || arg instanceof MultipartFile)).findFirst();
    }

    /**
     * 对比获取方法HttpMethod类型
     *
     * @param method 方法类型
     * @return HttpMethod
     * @author sutao
     * createDate 2022/9/8 22:03
     * <p>
     * editDescription:
     * editUser sutao
     * editDate 2022/9/8 22:03
     */
    private HttpMethod getHttpMethod(String method) {
        return HttpMethod.valueOf(method);
    }

}
